/*
 * XMLStrategyGenerator.java
 *
 * Created on 8 de Março de 2008, 1:11
 *
 */

package intopo.game;

import org.w3c.dom.Element;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import java.util.Map;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

import cern.jet.random.engine.RandomEngine;
import cern.jet.random.engine.MersenneTwister;

import util.xml.XML;
import util.xml.XMLParseException;
import util.xml.AttributeParser;
import util.xml.ParseAsInt;
import util.xml.CheckExistenceAttribute;

/**
 * This class generates player's strategies using the specifications stored
 * in a XML document. Classes in this package that represent player's
 * strategies all have a constructor that accepts a XML element.

 * <p> The XML document must contain a top {@code Players} tag where information
 * about how many strategies should be generated and where they can be placed. The tags inside this one have the name of a strategy constructor.  They can have two attributes: {@code howMany} and {@code position}. The first specifies how many instances should be constructed, while the second specifies the position in the population of an instance.
 * 

 * <p> The format of the XML document must obey the following structure:
 * <pre>
 * &lt;Players&gt;
 *     &lt;Constructor position="i"&gt;
 *         ...
 *     &lt;/Constructor&gt;
 *     ...
 *     &lt;Constructor howMany="x"&gt;
 *         ...
 *     &lt;/Constructor&gt;
 *     ...
 * &lt;/Player&gt;
 * </pre>

 * @author Pedro Mariano
 */
public class XMLStrategyGenerator<S extends Strategy<S>>
	implements StrategyGenerator<S>
{
	/**
	 * Array where player's strategies are stored.
	 */
	final private Object[] playerStrategy;
	/**
	 * Index to next strategy returned by method {@code next()}
	 */
	private int index = 0;

	/**
	 * Random number generator shared by all players' strategy. If threads are to be used and the random engine is not synchronised, then this field should be {@code null} and each thread should use its random engine.
	 */
	private final RandomEngine random;

	/**
	 * Creates a new instance of XMLStrategyGenerator
	 */
	public XMLStrategyGenerator (Node rootNode, Map<String, Constructor<? extends S>> map, boolean sharedRandomEngine)
		throws
			XMLParseException,
			InstantiationException,
			IllegalAccessException,
			InvocationTargetException
	{
		random = (sharedRandomEngine ? new MersenneTwister (new java.util.Date ()) : null);
		playerStrategy = new Object [parseDOMforNumber (rootNode)];
		parseDOMforStrategy (rootNode, map);
		index = 0;
	}

	public int numberStrategies ()
	{
		return playerStrategy.length;
	}

	public S next ()
	{
		if (index == playerStrategy.length) {
			throw new IllegalStateException ();
		}
		else {
			return (S) playerStrategy [index++];
		}
	}

	/**
	 * Parse the DOM and calculate how many player's strategies should be
	 * generated.

	 * <p> Calculates the maximum of strategy's positions specified and the
	 * sum of how many strategies should be created. The returned value is
	 * the maximum of these two calculated values.

	 * @return The number of strategies that are going to be generated.
	 */
	private int parseDOMforNumber (Node rootNode)
		throws XMLParseException
	{
		int result = 0;
		int numberPositions = 0;
		Element playersElement = (Element) XML.getChildNodeWithNameType (rootNode, "Players", Node.ELEMENT_NODE);
		if (playersElement == null)
			throw new XMLParseException ("Tag <Players> not found in the root of the document");
		NodeList nl;
		int i;
		AttributeParser<Integer> ap1;
		ap1 = new AttributeParser<Integer>
			(new CheckExistenceAttribute<Integer> (),
			 new ParseAsInt (0, Integer.MAX_VALUE));
		// First process the tags that specify the position of a player's strategy
		nl = XML.getChildElementsWithAttribute (playersElement, "position");
		i = nl.getLength ();
		while (--i >= 0) {
			Element element = (Element) nl.item (i);
			int position = ap1.parse (element, "position");
			if (position > result) {
				result = position;
			}
			numberPositions++;
		}
		// Second process the tags that specify how many player's strategies should be created
		nl = XML.getChildElementsWithAttribute (playersElement, "howMany");
		i = nl.getLength ();
		int howMany = 0;
		while (--i >= 0) {
			Element element = (Element) nl.item (i);
			howMany += ap1.parse (element, "howMany");
		}
		if (howMany > result - numberPositions) {
			result = howMany + numberPositions;
		}
		// Check if all positions are occupied
		if (numberPositions + howMany < result) {
			throw new XMLParseException ("Not enough players specified in config file: there are " + (result - numberPositions - howMany) + " open positions remaining");
		}
		return result;
	}

	/**
	 * This method parses the DOM to initialize the player's strategy vector. The
	 * parameter {@code rootNode} should refer to the root of the
	 * document. This document must contain a tag {@code <Players>} which
	 * contains the information about the players to be instantiated. The
	 * name of the tags under tag {@code <Players>} should match to the keys
	 * in the map {@code map}. The elements in this map are constructors
	 * which are used to create the players' instances.

	 * <p>The child tags of tag {@code <Players>} are processed according to
	 * the following order:

	 * <ol> <li>Tags with attribute {@code position} are first processed
	 * since they specify the position the player is going to occupy;</li>

	 * <li> Tags with attribute {@code howMany} are then processed has they
	 * specify an absolute quantity of players.</li></ol>

	 * @param rootNode The node that refers to the root of the document.
	 * @param map The map that contains mappings between strings and the
	 * players' constructors.

	 * @see #playerStrategy

	 * @throws XMLParseException If an error occurred when parsing the
	 * document.
	 */
	private void parseDOMforStrategy (Node rootNode, Map<String, Constructor<? extends S>> map)
		throws
			XMLParseException,
			InstantiationException,
			IllegalAccessException,
			InvocationTargetException
	{
		Element playersElement = (Element) XML.getChildNodeWithNameType (rootNode, "Players", Node.ELEMENT_NODE);
		if (playersElement == null)
			throw new XMLParseException ("Tag <Players> not found in the root of the document");
		NodeList nl;
		int i;
		AttributeParser<Integer> ap1;
		ap1 = new AttributeParser<Integer>
			(new CheckExistenceAttribute<Integer> (),
			 new ParseAsInt (0, playerStrategy.length));
		// First process the tags that specify the position of the player
		nl = XML.getChildElementsWithAttribute (playersElement, "position");
		i = nl.getLength ();
		while (--i >= 0) {
			Element element = (Element) nl.item (i);
			int position = ap1.parse (element, "position");
			String nodeName = element.getNodeName ();
			if (map.containsKey (nodeName)) {
				if (playerStrategy [position] != null) {
					throw new XMLParseException ("Position " + position + " is already taken");
				}
				Constructor<? extends S> c;
				c = map.get (nodeName);
				S player = c.newInstance (element, random);
				playerStrategy  [position] = player;
			}
			else {
				throw new XMLParseException ("Unkown tag " + nodeName + " in tag <Players>");
			}
		}
		// Second process the tags that specify how many players should be created
		nl = XML.getChildElementsWithAttribute (playersElement, "howMany");
		i = nl.getLength ();
		while (--i >= 0) {
			Element element = (Element) nl.item (i);
			int howMany = ap1.parse (element, "howMany");
			String nodeName = element.getNodeName ();
			if (map.containsKey (nodeName)) {
				Constructor<? extends S> c;
				c = map.get (nodeName);
				int index = 0;
				while (howMany-- > 0) {
					while (index < playerStrategy.length
							 && playerStrategy [index] != null) {
						index++;
					}
					S player = c.newInstance (element, random);
					playerStrategy [index] = player;
				}
			}
			else {
				throw new XMLParseException ("Unkown tag " + nodeName + " in tag <Players>");
			}
		}
	}
}
